#ifndef __LOGGER_H__
#define __LOGGER_H__

/// \file Logger.h Provides a common logging interface.


/**
 \class Logger 
 Provides a common logging interface.

 \section Configuration
 
 Each Logger has a single configuration value called @<name>_dest
 which accepts a comma seperated list of filenames to output that
 log to, or the value "none" which will disable the log.  

 The special file names "-" and "stdout" each output to stdout, while
 the file name "stderr" sends output to stderr.

 Each filename can be followed by a colon and single letter to
 designate the lowest severity level to send to the log.  Currently
 there exists 'd' for debug, 'i' for info, 'w' for warning, and 'e'
 for error. All severity levels above the lowest are also sent to
 the log.  If no severity level is given, info will be the lowest.

 \subsection Examples

   Send all messages to output.log and errors to stdout and stderr:
     \verbatim
     --log_dest output.log:d,-:e,stderr:e
     \endverbatim

   Send all warnings and errors to stdout (no debug and info messages):
    \verbatim
     --log_dest -:w
    \endverbatim

   Disable log:
    \verbatim
     --log_dest none
    \endverbatim

 \section Usage

 Each Logger class represents a different log.  The constructor
 takes a name, default destination, and an optional bool
 configurable and string description.  The name is used to create the
 configuration option by adding "_dest" to the end of it.  The
 default destination is used if the simulator is run without setting
 that configuration option (see above).  If configurable is false,
 no configuration option is created and the default destination is
 always used. The description is used in the help output of the
 configuration option and defaults to the name when not specified.

 Once the Logger is created there are four functions (logically)
 that write to the log at varying severity levels: debug, info,
 warning, error.  For convenience the functions d, i, w, e and
 defined and do the same thing as their longer counterparts. Each
 function takes a printf argument list.

 exit, sprintf, snprintf, atoi, vsnprintf, and vsprintf are all
 defined as global functions and can be used in place of the libc
 counterparts without worrying about the libc buffering.

 \section Prefixes 
 
 Prefix callbacks are called at the beginning of each line
 printed by that Logger, regardless of where the \n's fall in
 the actual logging calls.

 A prefix callback is registered via addPrefixCB(), and has the
 signature:
 \verbatim
 char* callback(char* string, Entity* e, void* data); 
 \endverbatim
 It takes in a string to print to, the Entity associated with
 the Logger (see below), and any extra data that was supplied
 when the callback was registered.  It should add its prefix to
 the string (using sim_sprintf) and return a pointer to the end
 of where it has written (not the number of chars written like
 sprintf does, maybe it should?).  

 The Entity supplied to the prefix function comes from one of
 three places.

 \li Each time the Simulator ticks an Entity, it tells the Logger
   what Entity it is about to tick. 

 \li Each Logger can have an entity associated with it, supplied
   at construction.  If supplied, this Entity will always be
   used instead of the currently ticking Entity.  

 \li Each output function (d{ebug}, i{nfo}, w{arning}, and
   e{rror}), now takes an optional Entity* as its first
   parameter.  If supplied, this argument will always be given
   to the prefix function regardless of the currently ticking
   Entity or the Entity associated with the Loger. 

 If the Entity is NULL (meaning no Entity is being ticked, and
 no Entity was supplied), for example, in initial printing of
 the environment and when the Pool is logging, no prefix
 callbacks will be called.

 If the Entity equals the constant NO_PRE, no prefix callbacks
 will be called.  This can be used to prevent prefixs for a
 particular output call, i.e.:
 \code
 log->d(NO_PRE, "This won't have a prefix.");
 \endcode

 \section Categories
 
 Each Logger also has a "category name" supplied at creation, this
 should be a short name (1, 2, maybe 3 chars) used in the prefix.
 This is different than the Logger's name, which is used for the
 commandline option controlling this logger.  This makes it possible
 to not only have english commandline options and short categories
 printed in the log, but allows multiple different commandline
 option controlled Loggers belong to the same category. (i.e. we
 could have "busLog" and "memoryLog" both belong to the "m" category
 or something).  I have supplied some obvious, but maybe not
 optimal, category names to all the Logger's created in the code,
 feel free to change them.

 \subsection Default prefix callback
 
 There also the option at creation (defaulting to false) to add a
 default prefix to every line.  The default prefix looks like:
 \verbatim
 0L1:12345:c|
 \endverbatim

 The first field is the Entity name, the second is the simclock, the
 third is the Logger's category (if non-empty).

 */

#include <stdarg.h>

#include "base/Entity.h"
#include "util/Pool.h"
#include "util/List.h"

enum SEVERITY {
    SEV_DEBUG,
    SEV_INFO,
    SEV_WARNING,
    SEV_ERROR
};

static const int MAX_DESTS = 10;
static const int NUM_SEVS = SEV_ERROR + 1;
static const int MAX_FILES = NUM_SEVS * MAX_DESTS;
static const int FILENAME_SIZE = 1024;
static const int PRINTBUFFER_SIZE = 8192;
static const int MAX_PATH = 1024;

extern void sim_exit (int rc);
extern int sim_sprintf (char* buffer, const char* text, ...)
    __attribute__ ((format (printf, 2, 3)));
extern int sim_snprintf (char* buffer, size_t size, const char* text, ...);
extern int sim_atoi (const char* buf, const char** newbuf);
extern int sim_vsnprintf (char* buffer, int max, const char* text, va_list ap);
extern int sim_vsprintf (char* buffer, const char* text, va_list ap);

#define DECL_PRINT_FUNC(name)                          \
    void name (const char* text, ...)                         \
        __attribute__((format (printf, 2, 3)));         \
    void name (Entity* e, const char* text, ...)              \
        __attribute__((format (printf, 3, 4))); 

#define DECL_PRINT_FUNCS(name, letter)         \
    DECL_PRINT_FUNC(name);                      \
    DECL_PRINT_FUNC(letter);            

#define NO_PRE (Entity*)-1

class Logger {
  public:
    NEW_DELETE_OPERATORS_H(Logger,NoSuper);
    Logger(const char* name,
           const char* cat,
           const char* default_dest,
           const char* desc=NULL,
           Entity* _entity=NULL,
           bool doDefaultPrefix=false,
           bool configurable=true);
    ~Logger();

    void newSeverity (int low_sev);
    int  getSeverity () { return severity; }

    /** 
        getOutputPath qualifies a filename with the current simulator
        output_path. all files should be opened relative to the
        output_path to ensure they can be collected by the robot.
    */
    static void getOutputPath(char* buffer, const char* filename);
    void debugHex (uint const* data, uint len);

    // debug: actual debug output, volumous...
    DECL_PRINT_FUNCS(debug, d);

    // info: goes only to simulator output file
    DECL_PRINT_FUNCS(info, i);

    // warning: goes to simulator output file and stdout
    DECL_PRINT_FUNCS(warning, w);

    // error: goes to simulator output file and stdout and exits
    DECL_PRINT_FUNCS(error, e);

    void output (SEVERITY sev, const char* text, ...)
        __attribute__ ((format (printf, 3, 4)));
    void output (Entity* e, SEVERITY sev, const char* text, ...)
        __attribute__ ((format (printf, 4, 5)));
    void o (SEVERITY sev, const char* text, ...)
        __attribute__ ((format (printf, 3, 4)));
    void o (Entity* e, SEVERITY sev, const char* text, ...)
        __attribute__ ((format (printf, 4, 5)));
    
    /**
       dump a stack trace
    */
    void printStackTrace (SEVERITY sev);

    /**
       dump the address space layout
    */
    void printMemoryMap (SEVERITY sev);

    void outputHex (SEVERITY sev, uint const* data, uint len);
    
    static void writeOrDie (int fd, char* buffer);
    static void setCurEntity (Entity* e);
    void addPrefixCB (char* (*func)(char*, Entity*, void*), void* data);

    // needs to be public for Pool
    typedef struct prefixCB {
      public:
        char* (*func)(char*, Entity*, void*);
        void* data;
    } prefixCB;
    
  private:
    /// describes an open file
    typedef struct {
        /**
           the specified path such as log.sim
        */
        char filename[FILENAME_SIZE];
        /**
           the filename qualified by the output_path such as output/test-less/foo/log.sim
        */
        char filepath[FILENAME_SIZE];
        /**
           the qualified filename with wrapsize extension such output/test-less/foo/log.sim.old
        */
        char wrappath[FILENAME_SIZE];
        /** 
            the open file descriptor
         */
        int fd;
        /** 
            reference count for how many Loggers are sharing this open_file
        */
        int count;
        /** 
            when non-zero, current file should be saved and we should
            wrap output to a new file 
        */
        int wrapsize;
        /**
           estimated number of bytes written. not exact because of
           things like printStackTrace which calls
           backtrace_symbols_fd which does not increment the count
        */
        int written;
    } open_file;

    const char* name;
    char* cat;
    Entity* entity;

    static Entity* curEntity;
    static bool initializedClass;
    static void initializeClass ();
    static void init_openfiles ();
    static bool log_debug;
    static bool loopOnError;
    static char output_path[MAX_PATH];
#ifdef __linux__
    static char proc_pid_maps_path[MAX_PATH];
    void memory_map_fd (int out);
#endif
    static char buffer[PRINTBUFFER_SIZE];
    static char formatBuffer[PRINTBUFFER_SIZE];

    bool configurable;

    int severity;
    open_file* dest[NUM_SEVS][MAX_DESTS];
    int num_dests[NUM_SEVS];
    open_file* open_files[MAX_FILES];
    int num_open_files;

    static open_file gbl_open_files[MAX_FILES];
    static int gbl_num_open_files;

    void assignDest (int low_sev, open_file* dest);
    void openFile (const char* filename, char sev='\0');
    int openOrDie (const char* filename);
    void closeOrDie (int fd);
    void bt_exit (int code);

    bool doPrefix;
    List<prefixCB*> prefixs; // if not static, Pool complains we didn't delete its own
    static char* defaultPrefix(char* index, Entity* e, void* data);
    char* putPrefix (char* index, Entity* e);

    int bufferLeft (char* index);
    void _output (SEVERITY sev, const char* text, va_list ap, Entity* e=NULL);
};

extern Logger* Log;
extern Logger* nullLog;

#ifndef ASSUME
#ifndef ASSERTS_DISABLED
#define ASSUME(x)  {if (!(x)) \
{Log->warning("%s:%d: (%s) assumption failed: %s\n",\
 __FILE__, __LINE__, __func__, # x);} }
#define ASSERT(x)  {if (!(x)) \
{Log->error("%s:%d: (%s) assertion failed: %s\n",\
 __FILE__, __LINE__, __func__, # x);} }
#else
#define ASSUME(x)
#define ASSERT(x)
#endif
#endif

#define NOTREACHED() \
{Log->error("%s:%d: NOTREACHED() error\n",\
 __FILE__, __LINE__);} 


#endif // __LOGGER_H__
